/**
    Implementation of the communication functionalities of LibLC.

*/
#include <sys/shm.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "../Include/channel.h"
#include "../Include/shm_common.h"

/*
    General procedure for the creation of a communication channel.
    If it is a synchronous channel, it will have k=0; otherwise is an

    asynchronous channel.
    The channel will contain k+1+j vtgs; j has to be at least 1, for the channel to be safe.
*/
inline struct channel_t * __LCcreate( key_t key, size_t size, int k, int j, ChannelType type )
{
    struct channel_t *ch;
    int fixedsize = size + ( LINE_SIZE - size%LINE_SIZE ); //padding to cache line size

    /*
        Create a shared memory segment for the channel.
        It's structured in the following way
        - the channel descriptor
        - the space for target variables (k+1+j)
        - the validity bit (actually byte) vector
    */
    int id = shmget( key, sizeof(*ch) + fixedsize*(k+1+j)+sizeof(bool)*(k+1+j), IPC_CREAT | 0700 );
    if( id == -1 )
    {
        fprintf(stderr, "[LCcreate] shmget(%x[%d], size=%d, %x): %s\n", key, key, (int) ( sizeof(*ch) + fixedsize*(k+1+j) ), IPC_CREAT | 0700, strerror(errno) );
        return NULL;
    }

    //attach of the channel, in order to have the base address of the channel descriptor
    ch = shmat( id, 0, 0 );
    if( ch == (void*) -1 )
    {
        printf( "[LCcreate] shmat(%d, %d, %d): %s\n", id, 0, 0, strerror(errno) );
        return NULL;
    }

    // setting channel type
    ch->type = type;

    // setting channel key and id
    ch->key = key;
    ch->id = id;

    /*
        For the creation of condition variable, we can't use a traditional approach (that will
        allocate memory in the heap, hence not in the shared memory segment).
    */
    initLockVariable(&(ch->mutex));
    initCondVariable(&(ch->cond_tosender));
    initCondVariable(&(ch->cond_toreceiver));

    // setting other fields of the channel
    ch->size = size;
    ch->fixedsize = fixedsize;
    ch->messages = 0;
    ch->k = k;
    ch->j = 1+j;
    ch->nextVtg = 0;
    ch->currVtg = -1;
    ch->altKey = 0;
    return ch;
}

/*
    Creation of a synchronous channel. It's a simple wrapper for the
    general procedure __LCcreate
*/
struct channel_t *LCcreate_ss( key_t key, size_t size,  int j )
{
    return __LCcreate(  key,  size,  0,  j, SYNCHRONOUS_SYMMETRIC );
}

/*
    Creation of a asynchronous channel. It's a simple wrapper for the
    general procedure __LCcreate
*/
struct channel_t *LCcreate_as( key_t key, size_t size, int k, int j )
{
    return __LCcreate(  key,  size,  k,  j , ASYNCHRONOUS_SYMMETRIC);
}

/*
    Returns the size of the channel data type
*/
inline size_t LCgetChannelTypeSize( struct channel_t *ch )
{
    return ch->size;
}


inline bool LCreadyToReceive( struct channel_t *ch )
{
    /*
        We have to check if the number of messages is greater than zero but also that
        the relative validity byte is true (otherwise the correspondent message is meaningless)
    */
    int currVtg = ( ch->currVtg + 1 ) % (ch->k + ch->j);
    //printf("--ready to receive -- messages=%d validity=%d\n", ch->messages ,*VALB(ch,currVtg));
    return (ch->messages > 0 && *VALB(ch,currVtg));
}


inline bool LCreadyToSend( struct channel_t *ch )
{
    /*
        In case of synchronous channel, we can send if messages=0
    */
    return ch->messages <= ch->k;
}

/*
    Maps a shared channel on the virtual memory
*/
struct channel_t *LCattach( key_t key)
{
    /* we have to acquire the shared segmet twice: the first one to get the channel * struct.
    Then we know the properties of the channel (type, size, asynch degree...) and we can attach it
    properly*/
    struct channel_t *tmpCh, *ch;

    //acquire the channel descriptor infos
    int id = shmget( key, sizeof(*ch), 0700 );
    if( id == -1 )
    {
        fprintf(stderr, "[LCattach] shmget(): %s\n", strerror(errno) );
        return NULL;
    }

    tmpCh = shmat( id, 0, 0 );
    if( ! tmpCh )
    {
        fprintf(stderr, "[LCattach] shmat(): %s\n", strerror(errno) );
        return NULL;
    }

    // now we can acquire the whole channel descriptor
    key = tmpCh->key;
    size_t size = tmpCh->fixedsize;
    int k = tmpCh->k; //0 in case of synchronous channel
    int j = tmpCh->j-1;

    id = shmget( key, sizeof(*ch) + size*(k+1+j)+sizeof(bool)*(k+1+j), 0700 );
    // freeing tmp channel
    shmdt( tmpCh );
    if( id == -1 )
    {
        fprintf( stderr, "[LCattach] shmget(): %s\n", strerror(errno) );
        return NULL;
    }

    ch = shmat( id, 0, 0 );
    if( ch == (void*) -1 )
    {
        fprintf( stderr, "[LCattach] shmat(): %s\n", strerror(errno) );
        return NULL;
    }
    return ch;
}


/*
    Destroys a channel
*/
void LCdestroy( struct channel_t *ch )
{
    //Mark  the  segment to be destroyed.
    shmctl( ch->id, IPC_RMID, 0 );
}

/*
    Removes a channel from the virtual memory
*/
void LCdetatch( struct channel_t *ch )
{
    //detatch the shared memory segment
    shmdt( ch );
}

/*
    Sends a message on a channel
*/
struct delegation_t* LCsend( struct communicator_t *kp,struct channel_t *ch, char *msg )
{

    //printf("Delego send sul canale :%d\n",ch->id);

    //create a delegation that will contains all the necessary infos
    struct delegation_t *dg=malloc(sizeof(struct delegation_t));
    dg->ch=ch;
    dg->msg=msg;
    dg->buffered=false;
    //init lock and cond variable
    initLockVariable(&(dg->lV));
    initCondVariable(&(dg->ticket));



    //update the channel
    __LClockChannel( ch );
    ch->messages ++;
    //wait if necessary
    while( ! LCreadyToSend(ch) )
        waitNotify(&(ch->cond_tosender), &(ch->mutex));
    __LCunlockChannel( ch );

      //enqueue the delegation
    enqueue(kp->dQueue,dg);

    //printf("Fine send.worker\n");

    /*
        Copy of the message. Since memcpy has problem with the alignment,
        we copy all the blocks using it and then what remains byte per byte.
    */
    /*    memcpy( MSG(ch,ch->nextVtg), msg, ch->fixedsize - LINE_SIZE );
        int i;
        for( i = ch->fixedsize - LINE_SIZE; i < ch->size; i ++ )
        {
            *( MSG(ch,ch->nextVtg)+i ) = *( msg+i );
        }


        //set the validity byte
        *VALB(ch,ch->nextVtg)=true;
        ch->nextVtg = ( ch->nextVtg + 1 ) % ( ch->k + ch->j ); //k=0 for synchr channel

        //wakeup partner (if necessary)
        notify(&(ch->cond_toreceiver));

    */

    /*
        alternative command: if there is the altKey field setted means that
        someone is polling on the alternative command. Wakeup it...
    */
    /* TODO (dematteis#1#): che se fa con l'alternativo??? */

    /*if( ch->altKey )
    {
        __LCwakeUpAlternative( ch->altKey );
    }*/

    //  __LCunlockChannel( ch );
    return dg;
}

Delegation LCbsend( Communicator kp, Channel ch, char *msg )
{

    //create a delegation that will contains all the necessary infos
    struct delegation_t *dg=malloc(sizeof(struct delegation_t));
    dg->ch=ch;
    dg->buffered=true;
    //init lock and cond variable
    initLockVariable(&(dg->lV));
    initCondVariable(&(dg->ticket));

    //if the delegation queue is empty, the copy will be performed
    //by the communicator, otherwise we will make it
    if(getActualSize(kp->dQueue)==0)
    {
        #ifdef COMM_DEBUG
        printf("[Worker] buffered send, delego senza copiare, msg allocato a %p\n",msg);
        #endif
        dg->msg=msg;
        dg->to_be_copied=true;
        //enqueue the delegation
        enqueue(kp->dQueue,dg);
    }
    else
    {
        #ifdef COMM_DEBUG
        printf("[Worker] buffered send, delego con copia\n");
        #endif
        //copy the message
        char *copy=malloc(sizeof(ch->size));
        memcpy(copy,msg,ch->size);
        dg->msg=copy;
        dg->to_be_copied=false;
        //enqueue the delegation
        enqueue(kp->dQueue,dg);

        //signal on the ticket that is safe to modify the original message
        spin_lock(&(dg->lV));
        notify(&(dg->ticket));
        spin_unlock(&(dg->lV));
    }

    //update the channel
    __LClockChannel( ch );
    ch->messages ++;
    //wait if necessary
    while( ! LCreadyToSend(ch) )
        waitNotify(&(ch->cond_tosender), &(ch->mutex));

    __LCunlockChannel( ch );
    return dg;

}

void LCwait(struct delegation_t *dg)
{
    //wait for the notification

    spin_lock(&dg->lV);
    while(!hasAlreadyNotified(&dg->ticket))
    {
        waitNotify(&dg->ticket,&dg->lV);
    }
    spin_unlock(&dg->lV);

    /* TODO (dematteis#1#): Free la deve fare il caller magari! */
    //free
    free(dg);
}

/*
    Receives a message from the channel
*/
char *LCreceive( struct channel_t *ch )
{
    char *vtg;

    __LClockChannel( ch );

    //wait if necessary
    while( ! LCreadyToReceive(ch) )
    {
       // printf("---Receive--- aspetto\n");
        waitNotify(&(ch->cond_toreceiver), &(ch->mutex));
    }
    //update info
    ch->currVtg = ( ch->currVtg + 1 ) % (ch->k + ch->j);
    //get the pointer to the message
    vtg = MSG(ch, ch->currVtg);

    /*reset the validity byte: please note that this has the same problem of possibile overwriting of variables
        that we try to solve using J additional target variable.
        A better solution will be to have only the validity byte that will be resetted by the receiver
        when the usage of a particular target variable is terminated*/
    *VALB(ch,ch->currVtg)=false;

    ch->messages --;

    //wake up the partner if necessary
    notify(&(ch->cond_tosender));
    __LCunlockChannel( ch );

    return vtg;
}

/*
    Prints debugging info about a channel
*/
void printCH_as( struct channel_t *ch )
{
    int i;

    printf( "struct channel_t: %p\n"
            "    key: %d\n"
            "     id: %d\n"
            "   size: %d\n"
            "fxdsize: %d\n"
            "      k: %d\n"
            "      j: %d\n"
            "   next: %d\n"
            "   curr: %d\n"
            "   msgs: %d\n"
            "    alt: %d\n"
            "    tosender: %c\n"
            "    toreceiver: %c\n"
            "[%d bytes + %d per message: %d]\n",
            ch,
            ch->key,
            ch->id,
            ch->size,
            ch->fixedsize,
            ch->k,
            ch->j,
            ch->nextVtg,
            ch->currVtg,
            ch->messages,
            ch->altKey,
            ch->cond_tosender, ch->cond_toreceiver,
            (int) sizeof(*ch), ch->size, (int) sizeof(*ch)+ch->size );

    for( i = 0; i < (ch->k + ch->j); i ++ )
    {
        printf( " msg[%d]: %s\n", i, MSG(ch,i) );
    }
}
